//use concurrent stack now!
//no need follow strategy anymore!

//you should define SIZEX/SIZEY/SIZEZ/BLKX/BLKY/ITER
//actually 2d-partitioning in this program 

#include <stdlib.h>
#include <stdio.h>
#include <math.h>
#include <assert.h>

#include <sys/time.h>
#include "3d7p_aceMesh.h"
#include "aceMesh_runtime_c.h"
#include "task.h"
double A[2][SIZEX][SIZEY][SIZEZ]; //attribute ((aligned(64)));
//for test
double test[2][SIZEX][SIZEY][SIZEZ];

extern void  slave_stencil_core(targs* args) ;
//arg
#define FREQ 1500000000
#define alpha_d 0.0876
#define beta_d  0.0765
#ifdef _PROFILING
unsigned long slave_dma_count[64]={0};
unsigned long slave_comp_dma_count[64]={0};
unsigned long slave_comp_count[64]={0};
unsigned long total_comp_dma_count=0;
unsigned long total_dma_count=0;
unsigned long total_comp_count=0;
unsigned long rpcc()
{
  unsigned long time;
  asm("rtc %0":"=r"(time):);
  return time;
}
#endif
void my_neighbors(void** neighbor_addrs,int* num_neigh, targs* args)
{
   int starti=args->starti;
    int startj=args->startj;
    void* src_addr=args->src_addr;
  
    *num_neigh=0;
    if(starti - BLKX>=0)
        neighbor_addrs[(*num_neigh)++]=(void*)(src_addr - BLKX * SIZEY * SIZEZ * 8);
    if(starti + BLKX < SIZEX)
        neighbor_addrs[(*num_neigh)++]=(void*)(src_addr + BLKX * SIZEY * SIZEZ * 8);
    if(startj - BLKY >= 0)
        neighbor_addrs[(*num_neigh)++]=(void*)(src_addr - BLKY * SIZEZ * 8);
    if(startj + BLKY < SIZEY)
        neighbor_addrs[(*num_neigh)++]=(void*)(src_addr + BLKY * SIZEZ* 8);
}


int Do3d7p(const int iter, const int thread_num)
{
    targs each;
    int itr,i,j;
    int block[3];
    block[0]=BLKX ;
    block[1]=BLKY;
    block[2]=BLKZ;
    //block[3]=2;
    //printf("before_ldmbuf");
    //fflush(stdout);
    ldmbuf_add(block, 3, 2);
    //ldmbuf_bulid_end();

    //printf("before_iter,"); 
    //fflush(stdout);
    for(itr = 0; itr < iter; ++itr)
    {
        int tile_no[3][2];
        tile_no[0][0]= 0;
        tile_no[0][1]= floor((SIZEX-2)/BLKX)-1;
        tile_no[1][0]= 0;
        tile_no[1][1]= floor((SIZEY-2)/BLKY)-1;
        tile_no[2][0]= 0;
        tile_no[2][1]= floor((SIZEZ-2)/BLKZ)-1;
        acemesh_register_invalidatetab(&A[(itr+1)%2][0][0][0], 3, (int *)tile_no);
        acemesh_begin_split_task("3d7p iter");
        for(i = 1; i < SIZEX -1; i += BLKX)
        {
            int endi = (i + BLKX > SIZEX -1 ) ? SIZEX - 1 : i + BLKX;
            for(j = 1; j < SIZEY -1; j += BLKY)
            {
                int endj = (j + BLKY > SIZEY -1 ) ? SIZEY - 1 : j + BLKY;
                each.starti=i;
                each.endi=endi;
                each.startj=j;
                each.endj=endj;
                each.k_N=SIZEZ;
                each.iter=itr;
                each.src_addr=&A[(itr+1)%2][i][j][0];
                acemesh_push_wlist(1, &A[itr%2][i][j][0],NORMAL);
                acemesh_push_rlist(1, &A[(itr+1)%2][i][j][0], NORMAL);
                acemesh_task_generator_with_neighbors((TASK_FUNCPTR)slave_stencil_core,
                      (void*)(&each), sizeof(targs), 
                      NULL, (NEIGHBOR_FUNCPTR)my_neighbors, &each);
                acemesh_task_set_type(STENCIL_TASK);
            }
        }
        acemesh_end_split_task();
    }
    acemesh_spawn_and_wait(1);
    return 0;
}



void init()
{
    int i,j,k;
    for( i = 0; i < SIZEX; i++)
     for( j = 0; j < SIZEY; j++)
      for( k = 0; k < SIZEZ; k++){
        A[1][i][j][k] = (double)(i*2.5 + j*3.3 + k*0.5 ) /3.0; 
     }
}
int check(const int iter)
{
   int i,j,k;
   int itr;
   int ii,jj,kk;
   printf("begin check\n");
    for(i = 0; i < SIZEX; i++)
     for( j = 0; j < SIZEY; j++)
      for( k = 0; k < SIZEZ; k++){
         test[1][i][j][k] = (double)(i*2.5 + j*3.3 + k*0.5 ) /3.0; 
      }
       
    for(itr = 0; itr < iter; ++itr){
     for(i = 1; i < SIZEX - 1; i++)
      for( j = 1; j < SIZEY - 1; j++)
       for( k = 1; k < SIZEZ - 1; k++)
       {
           //if(i==7&&j==1&&k==2)
             // printf("master:%20.20f,%20.20f,%20.20f,%20.20f,%20.20f,%20.20f,%20.20f\n",
               //      test[(itr+1)%2][i][j][k],test[(itr+1)%2][i-1][j][k],
                 //    test[(itr+1)%2][i+1][j][k],test[(itr+1)%2][i][j-1][k],test[(itr+1)%2][i][j+1][k],
                   //  test[(itr+1)%2][i][j][k-1],test[(itr+1)%2][i][j][k+1]);
           test[itr%2][i][j][k] = alpha_d * test[(itr+1)%2][i][j][k] + \
                        beta_d * (test[(itr+1)%2][i-1][j][k] + test[(itr+1)%2][i+1][j][k] +\
                        test[(itr+1)%2][i][j-1][k] + test[(itr+1)%2][i][j+1][k] +\
                        test[(itr+1)%2][i][j][k-1] + test[(itr+1)%2][i][j][k+1] ); 
      }
    }
       
    for(ii = 1; ii < SIZEX - 1; ii++)
      for(jj = 1; jj < SIZEY - 1; jj++)
       for(kk = 1; kk < SIZEZ - 1; kk++){
          if( fabs(test[(iter+1)%2][ii][jj][kk]  - A[(iter+1)%2][ii][jj][kk]) > 1e-16 ){
             printf("i: %d   j:  %d k: %d\n",ii,jj,kk);    
             printf("test: %20.20f",test[(iter+1)%2][ii][jj][kk]);
             printf("but A: %20.20f",A[(iter+1)%2][ii][jj][kk]);
             printf("they are different!\n");               
             return -1;
         }
      }
  printf("correct\n");
  return 0;
}



int main(int argc, char** argv)
{
    int ITER,num_threads;
    int i,j,k;
    struct timeval start;
    struct timeval end;
    double total_time;       
    int p = 1;
#ifdef _PROFILING
    unsigned long st,et;
#endif
    ITER = 100;
    num_threads = 1;
    if(argc > 1)
        num_threads = atoi(argv[1]);
    if(argc > 2)
        ITER = atoi(argv[2]);
    if(((SIZEX-2)%BLKX) != 0 || ((SIZEY-2)%BLKY) != 0 || ((SIZEZ-2)%BLKZ) != 0)
    {   
      printf("(SIZEX-2)%BLKX) and ((SIZEY-2)%BLKY) and ((SIZEZ-2)%BLKZ) must == 0 \n");
      assert(0);
    } 
    init();
    gettimeofday(&start,NULL);
    //while(p);
    acemesh_runtime_init(num_threads);
#ifdef _PROFILING
    st=rpcc();
#endif
    Do3d7p(ITER, num_threads);
#ifdef _PROFILING
    et=rpcc();
#endif
    gettimeofday(&end,NULL);   
    acemesh_runtime_shutdown();  
#ifdef _PROFILING
    for(i=0;i<63;++i)
    {
         total_comp_dma_count+=slave_comp_dma_count[i];
         total_dma_count+=slave_dma_count[i];
         total_comp_count+=slave_comp_count[i];
    }
//    total_comp_dma_count=slave_comp_dma_count[0];
//    total_dma_count=slave_dma_count[0];
//    total_comp_count=slave_comp_count[0];
    printf("total_count=%6.3f\ntotal_comp_dma_count=%6.3f\ntotal_dma_count=%6.3f\ntotal_comp_count=%6.3f\n",
            (float)(et-st)/FREQ,
            (float)total_comp_dma_count/FREQ/num_threads,
            (float)total_dma_count/FREQ/num_threads,
            (float)total_comp_count/FREQ/num_threads);
#endif
#ifdef CHECK
    total_time = 0.0;
    //total_time+=(end.tv_sec-start.tv_sec)+(end.tv_usec-start.tv_usec)/1000000.0;
    if(!check(ITER))
        //printf("time: %lf\n",total_time);       
        printf("total_count: %6.3f\n",(float)(et-st)/FREQ);
    else 
    {
        printf("check_error\n");
        exit(1);
    }
#endif
    return 0;
}
